package org.finesys.common.file.client.oss.minio;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;

import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.entity.ContentType;
import org.finesys.common.file.FileInfo;
import org.finesys.common.file.client.FileTemplate;
import org.finesys.common.file.client.oss.OssProperties;
import org.finesys.common.file.properties.FileProperties;
import org.springframework.http.HttpMethod;

import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.Owner;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary;

import io.minio.BucketExistsArgs;
import io.minio.GetObjectArgs;
import io.minio.GetObjectResponse;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.ListObjectsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.ObjectWriteResponse;
import io.minio.PutObjectArgs;
import io.minio.RemoveBucketArgs;
import io.minio.RemoveObjectArgs;
import io.minio.Result;
import io.minio.errors.ErrorResponseException;
import io.minio.errors.InsufficientDataException;
import io.minio.errors.InternalException;
import io.minio.errors.InvalidResponseException;
import io.minio.errors.ServerException;
import io.minio.errors.XmlParserException;
import io.minio.http.Method;
import io.minio.messages.Item;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import okhttp3.OkHttpClient;

@RequiredArgsConstructor
public class MinioTemplate implements FileTemplate {

    private final FileProperties fileProperties;
    private MinioClient minioClient;

    @Override
    @SneakyThrows
    public void createBucket(String bucketName) {
        if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }


    @Override
    public FileInfo putObject(String bucketName, String objectName, InputStream stream, String contentType) throws Exception {
        return putObject(bucketName, objectName, stream, stream.available(), contentType);
    }

    @Override
    public FileInfo putObject(String bucketName, String objectName, InputStream stream) throws Exception {
        return putObject(bucketName, objectName, stream, stream.available(), ContentType.APPLICATION_OCTET_STREAM.getMimeType());
    }

    /**
     * 上传流
     */
    public FileInfo putObject(String bucketName, String objectName, InputStream stream, int size, String contentType) throws Exception {
        ObjectWriteResponse objectWriteResponse = minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(stream, size, -1).contentType(contentType).build());
        System.out.println(objectWriteResponse.object());
        String url = getObjectUrl(bucketName, objectName, 7);
        return new FileInfo()
                .setFileId(UUID.randomUUID().toString())
                .setFileName(objectName)
                .setOriginalFileName(objectName)
                .setBasePath(bucketName)
                .setUrl(url)
                .setSize(size)
                .setFileType(contentType);
    }


    @Override
    @SneakyThrows
    public void removeBucket(String bucketName) {
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    @Override
    @SneakyThrows
    public void removeObject(String bucketName, String objectName) throws Exception {
        minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    @Override
    @SneakyThrows
    public S3Object getObject(String bucketName, String objectName) {
        GetObjectResponse getObjectResponse = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
        S3Object s3Object = new S3Object();
        s3Object.setBucketName(bucketName);
        s3Object.setKey(objectName);
        s3Object.setObjectContent(getObjectResponse);
        return s3Object;
    }

    @SneakyThrows
    public Optional<Bucket> getBucket(String bucketName) {
        Optional<io.minio.messages.Bucket> bucket = minioClient.listBuckets().stream().filter(s -> s.name().equals(bucketName)).findFirst();
        Bucket bucket1 = new Bucket();
        bucket1.setName(bucket.get().name());
        bucket1.setCreationDate(Date.from(bucket.get().creationDate().toInstant()));
        return Optional.of(bucket1);
    }

    @Override
    @SneakyThrows
    public List<Bucket> getAllBuckets() {
        List<io.minio.messages.Bucket> listBuckets = minioClient.listBuckets();
        return listBuckets.stream().map(s -> {
            Bucket bucket = new Bucket();
            bucket.setName(s.name());
            bucket.setCreationDate(Date.from(s.creationDate().toInstant()));
            return bucket;
        }).collect(Collectors.toList());
    }


    @Override
    @SneakyThrows
    public List<S3ObjectSummary> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {
        List<S3ObjectSummary> objectSummaryList = new ArrayList<>();
        Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());
        results.forEach(itemResult -> {
            try {
                Item item = itemResult.get();
                S3ObjectSummary s3ObjectSummary = new S3ObjectSummary();
                s3ObjectSummary.setBucketName(bucketName);
                s3ObjectSummary.setKey(item.objectName());
                s3ObjectSummary.setETag(item.etag());
                s3ObjectSummary.setOwner(convertOwner(item));
                s3ObjectSummary.setStorageClass(item.storageClass());
                s3ObjectSummary.setLastModified(Date.from(item.lastModified().toInstant()));
                s3ObjectSummary.setSize(item.size());
                objectSummaryList.add(s3ObjectSummary);
            } catch (ErrorResponseException e) {
                e.printStackTrace();
            } catch (InsufficientDataException e) {
                e.printStackTrace();
            } catch (InternalException e) {
                e.printStackTrace();
            } catch (InvalidKeyException e) {
                e.printStackTrace();
            } catch (InvalidResponseException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (ServerException e) {
                e.printStackTrace();
            } catch (XmlParserException e) {
                e.printStackTrace();
            }
        });
        return objectSummaryList;
    }

    private Owner convertOwner(Item item) {
        Owner owner = new Owner();
        owner.setDisplayName(item.owner().displayName());
        owner.setId(item.owner().id());
        return owner;
    }

    /**
     * 获取共享文件外链 (GET请求)
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @return url
     */
    @Override
    public String shareObjectPreSignedURL(String bucketName, String objectName) {
        return shareObjectPreSignedURL(bucketName, objectName, 7);
    }

    /**
     * 获取共享文件外链 (GET请求)
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @param expires    过期时间 <=7天
     * @return url
     */
    @Override
    public String shareObjectPreSignedURL(String bucketName, String objectName, Integer expires) {
        return shareObjectPreSignedURL(bucketName, objectName, expires, null, HttpMethod.GET);
    }

    /**
     * 获取文件外链 (GET、PUT请求)
     *
     * @param bucketName bucket名称
     * @param objectName 文件名称
     * @param expires    过期时间 <=7天
     * @param params     请求参数
     * @param httpMethod GET、PUT请求
     * @return url
     */
    @SneakyThrows
    public String shareObjectPreSignedURL(String bucketName, String objectName, Integer expires, Map<String, String> params, HttpMethod httpMethod) {
        GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(objectName).method(convertMethod(httpMethod)).expiry(expires, TimeUnit.DAYS).build();
        if (params != null) {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                getPresignedObjectUrlArgs.extraQueryParams().put(entry.getKey(), entry.getValue());
            }
        }
        return minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs);
    }

    /**
     * 生成带过期时间的URL
     */
    public String getObjectUrl(String bucketName, String objectName, Integer expires) {
        return shareObjectPreSignedURL(bucketName, objectName, expires);
    }

    /**
     * 转换请求
     */
    private Method convertMethod(HttpMethod httpMethod) {
        switch (httpMethod) {
            case GET:
                return Method.GET;
            case POST:
                return Method.POST;
            case PUT:
                return Method.PUT;
            case HEAD:
                return Method.HEAD;
            case DELETE:
                return Method.DELETE;
            default:
                return Method.GET;
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        OssProperties properties = fileProperties.getMinio();
        // 客户端配置
        MinioClient.Builder builder = MinioClient.builder();
        builder.endpoint(properties.getEndpoint());
        builder.region(properties.getRegion());
        builder.credentials(properties.getAccessKey(), properties.getSecretKey());
        if (properties.isEnableSSL()) {
            builder.httpClient(okHttpClient());
        }
        minioClient = builder.build();
    }

    /**
     * SSL协议
     */
    private OkHttpClient okHttpClient() throws Exception {
        //SSL
        TrustManager[] trustManager = new TrustManager[]{
                new X509ExtendedTrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {

                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {

                    }

                    @Override
                    public void checkClientTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {

                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {

                    }

                    @Override
                    public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                }
        };
        SecureRandom secureRandom = new SecureRandom();
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustManager, secureRandom);
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
        okHttpClientBuilder.setSslSocketFactoryOrNull$okhttp(sslSocketFactory);
        HostnameVerifier hostnameVerifier = new DefaultHostnameVerifier();
        okHttpClientBuilder.setHostnameVerifier$okhttp(hostnameVerifier);
        return okHttpClientBuilder.build();
    }

}
